import type { Ref } from 'vue'
import type { NvFormItemOption } from '@/form/types'
import type { NvDict, NvOption, RowData } from '@/types'
import { inject, isRef, provide, ref, unref, watch, watchEffect } from 'vue'

const DICT_CACHE: Map<Function, UseDictReturn> = new Map()
const DICT_REQUEST_CACHE: Map<string, Promise<NvOption[]>> = new Map()

interface Params {
  form: RowData
  field: string
  option: NvFormItemOption
}

export interface UseDictReturn {
  dictData?: Ref<NvOption[]>
  loading?: Ref<boolean | undefined>
}

export function useDict(props: Params): UseDictReturn {
  const dict = props.option.dict
  const dictRequest = useDictRequest()
  if (!dict) {
    return {}
  }

  const loading = ref<boolean>()
  const dictData = ref<NvOption[]>([])
  /**
   * 最后一个获取字典的请求Promise，防止请求时长混乱，导致最后请求的数据被前面的请求的数据覆盖
   */
  let dictReqProms: Promise<NvOption[]>

  /**
   * 当 dict 为 url 字符串，获取字典数据
   * @param dictUrl 请求 url
   * @returns void
   */
  const handleDictUrl = (dictUrl: string) => {
    // 缓存
    if (DICT_REQUEST_CACHE.has(dictUrl)) {
      DICT_REQUEST_CACHE.get(dictUrl)!.then((res) => {
        dictData.value = res
      })
      return
    }

    // 获取字典请求处理函数
    if (!dictRequest) {
      return console.error(`[nv-crud]: dictRequest is not provided. Please use the 'dict-request' prop of the NvConfigProvider component to provide it.`)
    }

    // 请求字典数据
    loading.value = true
    const _proms = dictRequest!(dictUrl).then((res) => {
      // 确保最后一个请求有效
      if (dictReqProms === _proms) {
        dictData.value = res
      }
      return res
    }).finally(() => {
      if (dictReqProms === _proms) {
        loading.value = false
      }
    })
    // 添加缓存
    DICT_REQUEST_CACHE.set(dictUrl, _proms)
    dictReqProms = _proms
  }

  /**
   * 当 dict 为函数，处理函数的返回值
   * @param dictResult 函数的返回值
   */
  const handleDictFuncResult = async (dictResult: NvDict | Promise<NvDict>) => {
    if (Array.isArray(dictResult)) {
      dictData.value = dictResult
    }
    else if (typeof dictResult === 'string') {
      handleDictUrl(dictResult)
    }
    else if (dictResult instanceof Promise) {
      loading.value = true
      try {
        const _dict = await dictResult
        if (Array.isArray(_dict)) {
          dictData.value = _dict
        }
        else if (typeof _dict === 'string') {
          handleDictUrl(_dict)
        }
        else {
          dictData.value = []
          console.error(`[nv-crud]: Dict function for field "${props.field}" returned an unsupported type. Expected NvOption[]、string or Promise<NvOption[] | string>.`)
        }
      }
      finally {
        loading.value = false
      }
    }
    else {
      dictData.value = []
      console.error(`[nv-crud]: Dict function for field "${props.field}" returned an unsupported type. Expected NvOption[]、string or Promise<NvOption[] | string>.`)
    }
  }

  if (isRef(dict)) {
    watchEffect(() => {
      const unrefDict = unref(dict)
      if (Array.isArray(unrefDict)) {
        dictData.value = unrefDict
      }
      else if (typeof unrefDict === 'string') {
        handleDictUrl(dict.value as string)
      }
      else {
        console.error(`[nv-crud]: Unknown dict(${props.field}) type. Only types NvOption[], string, Ref<NvOption[] | string>, or Function are supported.`)
      }
    })
  }
  else if (Array.isArray(dict)) {
    dictData.value = dict
  }
  else if (typeof dict === 'string') {
    handleDictUrl(dict)
  }
  else if (typeof dict === 'function') {
    if (DICT_CACHE.has(dict)) {
      return DICT_CACHE.get(dict) as UseDictReturn
    }

    // 自定义代理对象：检查字典函数是否追踪了表单对象的字段
    const watchedFields: (string | symbol)[] = []
    const proxyForm = new Proxy(props.form, {
      get(target, property, receiver) {
        watchedFields.push(property)
        return Reflect.get(target, property, receiver)
      },
    })
    const dictResult = dict(proxyForm)
    handleDictFuncResult(dictResult)

    if (watchedFields.length) {
      // 动态字典，使用watch监听
      console.debug('watch dict: ', props.field, watchedFields.join(','))

      watch(
        watchedFields.map(field => () => props.form[field as string]),
        () => handleDictFuncResult(dict(props.form)),
      )
    }
    else {
      // 静态字典
      DICT_CACHE.set(dict, { dictData, loading })
    }
  }
  else {
    console.error(`[nv-crud]: Unknown dict(${props.field}) type. Only types NvOption[], string, Ref<NvOption[] | string>, or Function are supported.`)
  }

  return { loading, dictData }
}

export interface DictComponentSlotParams {
  rowData: RowData
  rowIndex: number
  dictData?: NvOption[]
  loading?: boolean
}

const DICT_FUNC_KEY = Symbol('dict-func')

export type DictRequest = (url: string) => Promise<NvOption[]>
export function provideDictRequest(func?: DictRequest) {
  if (func)
    provide(DICT_FUNC_KEY, func)
}

function useDictRequest() {
  return inject<DictRequest>(DICT_FUNC_KEY)
}
